iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0
Modern Web

Rails,我要進來囉系列 第 20

第二十天:在 Rails 7 + React JS 做出圈圈叉叉(Tic Tac Toe)

  • 分享至 

  • xImage
  •  

開場白

鼬~~哩賀,我是寫程式的山姆老弟,前幾天跟大家一起實驗了用 importmapwebpack、esbuild 來安裝 bootstrap,還有用 esbuild 安裝 React JS,今天來繼續延伸在 Rails 裡面搭配 React 的感覺,復刻一個之前我有做過的簡單小應用 - 圈圈叉叉,夠夠~

ps. 今天的專案就直接沿用昨天的專案。

做個圈圈叉叉的 React Component 吧

  1. 手動新增一個 app/javascript/components/TicTacToe.jsx 的 component 檔案

    // app/javascript/components/TicTacToe.jsx
    import React from "react";
    
    class TicTacToe extends React.Component {
        render() {
          return <div>This is TicTacToe</div>;
        }
    }
    
    export default TicTacToe;
    
  2. app/javascript/application.js 引用 TicTacToe component 並 render 出來

    // app/javascript/application.js
    import React from 'react';
    import { createRoot } from 'react-dom/client';
    
    import TicTacToe from './components/TicTacToe';
    
    const container = document.getElementById('app');
    createRoot(container).render(<TicTacToe/>);
    
  3. app/views/home/index.html.erb 新增一個 div 給 react component 綁定

    <!-- app/views/home/index.html.erb -->
    <h1>Home#index</h1>
    <p>Find me in app/views/home/index.html.erb</p>
    
    <div id="app"></div>
    
  4. 啟動 $ ./bin/dev,打開 127.0.0.1:3000,下認有下沒有成功嵌入

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day20-1.png

實作圈圈叉叉的功能

我的概念是把 九宮格 各自用 div 為一格來表示,我的方法比較笨,要把九個格子分別用九個 div 表達,如果有前端大神知道可以怎麼優化再麻煩留言告訴我 QQ

  1. 先把格子的版切出來,手動新增一個 wrapper class,並使用 grid layout

    /* app/assets/stylesheets/tictactoe.css */
    .wrapper {
        display: grid;
        grid-template-columns: 100px 100px 100px;
        grid-template-rows: 100px 100px 100px;
        grid-gap: 20px;
        align-items: center;
        justify-items: center;
    }
    
  2. app/javascript/components/TicTacToe.jsx 把九宮格畫出來,並加上點擊事件(click event)

    // app/javascript/components/TicTacToe.jsx
    import React from "react";
    
    class TicTacToe extends React.Component {
    
    		clicked(element) {
    			alert('clicked: ' + element.innerHTML)
    		}
    
        render() {
          return <div className="TicTacToe">
                    <div className="wrapper">
                        <div onClick={e => this.clicked(e.target)}>One</div>
                        <div onClick={e => this.clicked(e.target)}>Two</div>
                        <div onClick={e => this.clicked(e.target)}>Three</div>
                        <div onClick={e => this.clicked(e.target)}>Four</div>
                        <div onClick={e => this.clicked(e.target)}>Five</div>
                        <div onClick={e => this.clicked(e.target)}>Six</div>
                        <div onClick={e => this.clicked(e.target)}>seven</div>
                        <div onClick={e => this.clicked(e.target)}>eight</div>
                        <div onClick={e => this.clicked(e.target)}>Nine</div>
                    </div>
                    <div ref={this.winner}></div>
                </div>;
        }
    }
    
    export default TicTacToe;
    
  3. 啟動 $ ./bin/dev,打開 127.0.0.1:3000,確認一下點擊有沒有問題

  4. 成功~

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day20-2.png

  5. 再繼續加功能,這次把功能補齊,點擊後,要判斷輪到 O 或 X,然後把被點的字給改成 O 或 X,再判斷有沒有結束遊戲,沒有結束遊戲就繼續輪到下一位玩家,結束遊戲就顯示贏家是誰

    import React from "react";
    
    class TicTacToe extends React.Component {
    
        constructor(props) {
            super(props);
            this.one = React.createRef()
            this.two = React.createRef()
            this.three = React.createRef()
            this.four = React.createRef()
            this.five = React.createRef()
            this.six = React.createRef()
            this.seven = React.createRef()
            this.eight = React.createRef()
            this.nine = React.createRef()
    
            this.winner = React.createRef()
    
            this.players = ['O', 'X'];
            this.whosTurn = 0;
            this.game = true;
            this.clickableElements = [this.one, this.two, this.three, this.four, this.five, this.six, this.seven, this.eight, this.nine];
            this.WIN_CONDITION = [
                [1,2,3],
                [4,5,6],
                [7,8,9],
                [1,5,9],
                [3,5,7]
            ]
        }
    
        check() {
            var result = { X: [], O: [] }
            this.clickableElements.forEach ((element, index) => { 
              if (element.current.innerHTML === 'X') {
                result.X.push(index + 1)
              } else if (element.current.innerHTML === 'O') {
                result.O.push(index + 1)
              }
            })
            var winner = null
            this.WIN_CONDITION.forEach ((winArr) => {
              if (JSON.stringify(winArr) === JSON.stringify(result.X.sort())) {
                winner = 'X'
              } else if (JSON.stringify(winArr) === JSON.stringify(result.O.sort())) {
                winner = 'O'
              }
            })
            if (winner) {
              return winner
            }
        }
    
        clicked(clickedElement) {
            if (this.game) {
                var currentPlayer = this.whosTurn % this.players.length
                clickedElement.innerHTML = this.players[currentPlayer]
                var winner = this.check()
                console.log('Winner: ' + winner)
                if (winner) {
                  this.game = false
                  this.winner.current.innerHTML = 'Winner is ' + winner
                }
                this.whosTurn += 1
            }
        }
    
        render() {
          return <div className="TicTacToe">
                    <div className="wrapper">
                        <div ref={this.one} onClick={ e => this.clicked(e.target) }>One</div>
                        <div ref={this.two} onClick={e => this.clicked(e.target)}>Two</div>
                        <div ref={this.three} onClick={e => this.clicked(e.target)}>Three</div>
                        <div ref={this.four} onClick={e => this.clicked(e.target)}>Four</div>
                        <div ref={this.five} onClick={e => this.clicked(e.target)}>Five</div>
                        <div ref={this.six} onClick={e => this.clicked(e.target)}>Six</div>
                        <div ref={this.seven} onClick={e => this.clicked(e.target)}>seven</div>
                        <div ref={this.eight} onClick={e => this.clicked(e.target)}>eight</div>
                        <div ref={this.nine} onClick={e => this.clicked(e.target)}>Nine</div>
                    </div>
                    <div ref={this.winner}></div>
                </div>;
        }
    }
    
    export default TicTacToe;
    

    我這邊存變數用的方法很笨,九個格子就需要九個 ref,有沒有 React 大神幫忙開導一下我 QQ,還有我對 js 不太熟,寫起來很醜,希望有大神寫下重構過的版本,留言在下面~

  6. 再次啟動 $ ./bin/dev,打開 127.0.0.1:3000,確認一下整個遊戲

  7. 成功~

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day20-3.png

總結

雖然這個圈圈叉叉看起來有點銼,不過倒是讓我覺得很開心~ 竟然能在 Rails 裡使用 React,而且自己沒碰過 React 的情況下,竟然能東拼西湊出一個圈圈叉叉遊戲 XD,覺得很好玩

這接近十天連續體驗了 Rails 的各種前端方案,覺得現在對於 Rails 的前端更有點自信了,接下來我們將會回歸 RailsGuide 其他篇章,繼續探索我不熟 Rails 的部分,我們明天見~


上一篇
第十九天:在 Rails 7 使用 esbuild 來試用 React JS
下一篇
第二十一天:網頁如何支援多國語言?Rails 的 i18n
系列文
Rails,我要進來囉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言